/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.workflow.controller.startup;

import java.util.HashMap;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import offset.nodes.Constants;
import offset.nodes.server.model.RepositoryStartupExtension;

/**
 *
 * @author Walter Luetgenau
 */
public class WorkflowDocumentStartupExtension implements RepositoryStartupExtension {

    /**
     * The information about a workflow needed by the workflow document
     */
    class WorkflowInfo {

        String uuid; // the UUID of the workflow
        String creationState; // the state, that can be used to create new workflow documents

        public WorkflowInfo(String uuid, String creationState) {
            this.uuid = uuid;
            this.creationState = creationState;
        }

        public String getCreationState() {
            return creationState;
        }

        public String getUuid() {
            return uuid;
        }
    }
    Session systemSession;

    HashMap<String, WorkflowInfo> workflowCache = null;
    ConfiguredWorkflowsCacheUpdater updater;

    /**
     * Register an event listener for initializing new workflow documents
     * Called by the RepositoryExtensionsManager on startup.
     * 
     * @param systemSession
     */
    public void onStartup(Session systemSession) {
        try {
            this.systemSession = systemSession;

            updater = new ConfiguredWorkflowsCacheUpdater();
            updater.createCache();

            // when a workflow has been configured or deleted from configuration update cache
            systemSession.getWorkspace().getObservationManager().addEventListener(
                    updater,
                    Event.NODE_ADDED + Event.NODE_REMOVED,
                    Constants.CONFIG_WORKFLOW,
                    true,
                    null,
                    null,
                    true);

            // when a new workflow document node has been added
            systemSession.getWorkspace().getObservationManager().addEventListener(
                    new WorkflowDocumentEnricher(),
                    Event.NODE_ADDED,
                    "/",
                    true,
                    null,
                    null,
                    true);

        } catch (RepositoryException ex) {
            Logger.getLogger(WorkflowDocumentStartupExtension.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    class ConfiguredWorkflowsCacheUpdater implements EventListener {

        /**
         * Find the new state of the workflow.
         *
         * @param workflow
         * @return
         * @throws RepositoryException
         */
        protected String findCreationState(Node workflow) throws RepositoryException {
            NodeIterator ni = workflow.getNodes();
            while (ni.hasNext()) {
                Node state = ni.nextNode();
                if (!state.isNodeType(Constants.TYPE_STATE))
                    continue;

                if (state.hasProperty(Constants.PROP_NEW_STATE) && state.getProperty(Constants.PROP_NEW_STATE).getBoolean()) {
                    return state.getName();
                }
            }

            return null;
        }

        /**
         * Return a relative path from the root node.
         *
         * @param path
         * @return
         */
        protected String getRelativePath(String path) {
            if (path == null || path.length() == 0)
                return path;

            if (path.startsWith(Constants.PATH_SEPARATOR))
                return path.substring(1);

            return path;
        }

        /**
         * Create a cache of workflow information
         *
         * @throws RepositoryException
         */
        public void createCache() throws RepositoryException {
            Node root = systemSession.getRootNode();
            if (!root.hasNode(getRelativePath(Constants.CONFIG_WORKFLOW)))
                return;

            workflowCache = new HashMap<String, WorkflowInfo>();
            Node workflowDir = root.getNode(getRelativePath(Constants.CONFIG_WORKFLOW));
            NodeIterator workflows = workflowDir.getNodes();
            while (workflows.hasNext()) {
                Node workflow = workflows.nextNode();
                if (!workflow.isNodeType(Constants.TYPE_WORKFLOW)
                        || !workflow.hasProperty(Constants.PROP_DOCUMENT_TYPE))
                    continue;

                String creationState = findCreationState(workflow);
                if (creationState == null)
                    continue;

                workflowCache.put(workflow.getProperty(Constants.PROP_DOCUMENT_TYPE).getString(), new WorkflowInfo(workflow.getUUID(), creationState));
            }
        }

        @Override
        public void onEvent(EventIterator events) {
            try {
                createCache();
            } catch (RepositoryException ex) {
                Logger.getLogger(WorkflowDocumentStartupExtension.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * Add a workflow reference and the initial state to a workflow document node.
     * This can only be done, if there is a corresponding workflow document at
     * CONFIG_WORKFLOWS.
     */
    class WorkflowDocumentEnricher implements EventListener {

        public void onEvent(EventIterator events) {
            if (workflowCache == null)
                return; // no default workflows

            HashSet<String> processedPathes = new HashSet<String>();
            try {
                Node root = systemSession.getRootNode();

                while (events.hasNext()) {
                    Event event = events.nextEvent();

                    String path = event.getPath().substring(1);
                    if (event.getType() == Event.PROPERTY_ADDED || event.getType() == Event.PROPERTY_CHANGED || event.getType() == Event.PROPERTY_REMOVED)
                        path = path.substring(0, path.lastIndexOf("/"));

                    if (processedPathes.contains(path))
                        continue;
                    else
                        processedPathes.add(path);

                    if (!root.hasNode(path))
                        continue;

                    Node node = root.getNode(path);
                    if (!node.isNodeType(Constants.TYPE_WORKFLOW_DOCUMENT)
                            || node.hasProperty(Constants.PROP_WORKFLOW))
                        continue;

                    WorkflowInfo info = workflowCache.get(node.getPrimaryNodeType().getName());
                    if (info == null)
                        continue;

                    node.refresh(false);
                    if (!node.isCheckedOut())
                        continue;

                    node.setProperty(Constants.PROP_WORKFLOW, info.getUuid());
                    node.setProperty(Constants.PROP_CURRENT_STATE, info.getCreationState());
  
                    processedPathes.add(event.getPath());
                }

                systemSession.save();
            } catch (Throwable ex) {
                Logger.getLogger(WorkflowDocumentStartupExtension.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
